﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections.ObjectModel;
using System.Globalization;

namespace ResxLocalizationEditor {
	class ResourceSet {

		IList<string> _allStringKeys;
		IDictionary<CultureInfo, Resource> _resources;
        IDictionary<string, string> _comments;

		public ResourceSet(string baseResourcePath, ResourceWriter writer) {
			var files = ResourceHelper.GetFilesFromDefaultResource(baseResourcePath);

			this.LoadResourcesFromFiles(files, writer);

			HasChanged = false;
			OriginalResourcePath = baseResourcePath;
		}

        /// <summary>
        /// Builds an empty resource set with a default language.
        /// </summary>
        public ResourceSet() {
            HasChanged = true;

            _resources = new Dictionary<CultureInfo, Resource>();
            _comments = new Dictionary<string, string>();
            _allStringKeys = new List<string>();
        }

		public bool HasChanged { get; private set; }

		public string OriginalResourcePath { get; set; }

		public void LoadResourcesFromFiles(string[] files, ResourceWriter writer) {
			HashSet<string> allKeys = new HashSet<string>();
			_resources = new Dictionary<CultureInfo, Resource>(files.Length);
            _comments = new Dictionary<string, string>();

			foreach (var file in files) {
				var langCode = ResourceHelper.ExtractLanguageFromFilename(file);
                var lang = (langCode == null) ? null : new CultureInfo(langCode);

                //Load
				var res = new Resource(lang);
                writer.Load(res, file);

                //Merge keys
				foreach (var key in res.AllKeys)
					allKeys.Add(key);
                
                //Merge comments
                foreach (var commentPair in res.Comments) {
                    if (!_comments.ContainsKey(commentPair.Key))
                        _comments.Add(commentPair);
                    else {
                        var oldComment = _comments[commentPair.Key];
                        if (!oldComment.Equals(commentPair.Value)) {
                            //Attempt to do an ugly merge
                            var merged = string.Format("{0}\n{1}", oldComment, commentPair.Value);
                            _comments[commentPair.Key] = merged;
                        }
                    }
                }

				_resources.Add(lang, res);
			}

			//Sort alphabetically
            _allStringKeys = (from s in allKeys orderby s select s).ToList();
		}

		public void ExportSetToFile(string basePath, ResourceWriter writer) {
			string[] originalFiles = ResourceHelper.GetFilesFromDefaultResource(basePath);

            foreach (var orFile in originalFiles) {
                File.Delete(orFile);
            }

            foreach (var res in _resources.Values) {
                res.Comments = _comments;
                writer.Write(this, res, basePath);
            }

			/*var sortedResources = new SortedList<CultureInfo, Resource>(_resources, new CultureInfoComparer());
			var sortedFiles = new SortedList<string, string>(originalFiles.Length);
			foreach (string file in originalFiles)
				sortedFiles.Add(ResourceHelper.ExtractLanguageFromFilename(file), file);

			for (int iFiles = 0, iRes = 0; iRes < sortedResources.Count; ++iRes) {
				var resLang = sortedResources.Keys[iRes];

				if (iFiles < sortedFiles.Count) {
					//Still have files to match

					while (iFiles <= iRes && iFiles < sortedFiles.Count) {

						var fileLang = sortedFiles.Keys[iFiles];

						var cmp = fileLang.CompareTo(resLang);

						if (cmp < 0) {
							//Resource removed, delete file
							File.Delete(ResourceHelper.AppendLanguageToBaseFilename(basePath, fileLang));

							++iFiles;
						}
						else if (cmp == 0) {
							//Overwrite
                            sortedResources.Values[iRes].Comments = _comments;
                            writer.Write(this, sortedResources.Values[iRes], basePath);

							++iFiles;
						}
						else {
							//Resource added, write
                            sortedResources.Values[iRes].Comments = _comments;
                            writer.Write(this, sortedResources.Values[iRes], basePath);
							break;
						}
					}
				}
				else {
					//No more files to match, write
                    writer.Write(this, sortedResources.Values[iRes], basePath);
				}
			}*/

			HasChanged = false;
		}

		public int TotalNumberOfKeys {
			get {
				return _allStringKeys.Count;
			}
		}

		public IList<CultureInfo> Languages {
			get {
                return (from k in _resources.Keys
                        select k).ToList();
			}
		}

        public IList<Resource> Resources {
            get {
                return (from r in _resources.Values
                        orderby r.Language.Name
                        select r).ToList();
            }
        }

        public Resource this[CultureInfo language] {
            get {
                if (!_resources.ContainsKey(language))
                    return null;

                return _resources[language];
            }
        }

		public IList<string> Keys {
			get {
				return new ReadOnlyCollection<string>(_allStringKeys);
			}
		}

        /// <summary>
        /// Adds a resource to a resource set.
        /// </summary>
        public void AddResource(Resource resource) {
            if (resource == null || resource.Language == null)
                throw new ArgumentNullException();

            if (_resources.ContainsKey(resource.Language))
                throw new ArgumentException("Resource set already contains a resource for language " + resource.Language);

            _resources.Add(resource.Language, resource);

            //Integrate keys
            var hashset = new HashSet<string>(_allStringKeys);
            foreach (var newKey in resource.AllKeys)
                if (!hashset.Contains(newKey))
                    hashset.Add(newKey);
            _allStringKeys = (from k in hashset select k).ToList();

            //Integrate comments
            foreach (var newComment in resource.Comments) {
                if(!_comments.ContainsKey(newComment.Key))
                    _comments.Add(newComment);
            }
        }

		public void RenameKey(string oldValue, string newValue) {
			var index = _allStringKeys.IndexOf(oldValue);
			var newIndex = _allStringKeys.IndexOf(newValue);

			if (newIndex >= 0)
				throw new ArgumentException("Rename would cause a conflict on key " + newValue + ".");

			if (index < 0)
				throw new ArgumentException("Key " + oldValue + " not present.");

			_allStringKeys[index] = newValue;

			foreach (var pair in _resources) {
				pair.Value[newValue] = pair.Value[oldValue];
				pair.Value[oldValue] = null;
			}

			HasChanged = true;
		}

		public void AddKey(string keyName) {
			var index = _allStringKeys.IndexOf(keyName);

			if (index >= 0)
				throw new ArgumentException("Key " + keyName + " already present.");

			_allStringKeys.Add(keyName);

			HasChanged = true;
		}

		public void RemoveKey(string keyName) {
			var index = _allStringKeys.IndexOf(keyName);

			if (index < 0)
				throw new ArgumentException("Key " + keyName + " not present.");

            //Remove key
			_allStringKeys.RemoveAt(index);

            //Remove values and comment
			foreach (var pair in _resources) {
				pair.Value[keyName] = null;
			}
            _comments.Remove(keyName);

			HasChanged = true;
		}

		public string GetStringValue(string key, CultureInfo lang) {
			if (!_resources.ContainsKey(lang))
				throw new ArgumentException("Language " + lang + " has no resources associated.");

			return _resources[lang][key];
		}

		public void SetStringValue(string key, CultureInfo lang, string value) {
			if (!_resources.ContainsKey(lang))
				throw new ArgumentException("Language " + lang + " has no resources associated.");

			_resources[lang][key] = value;

			HasChanged = true;
		}

		public void AddLanguage(CultureInfo lang) {
            if (_resources.ContainsKey(lang))
                throw new ArgumentException("Language + " + lang + " is already in the current set.");

            _resources.Add(lang, new Resource(lang));

			HasChanged = true;
		}

		public void RemoveLanguage(CultureInfo lang) {
            if (!_resources.ContainsKey(lang))
                throw new ArgumentException("Language " + lang + " has no resources associated.");

            _resources.Remove(lang);
			HasChanged = true;
		}

        public string GetComment(string key) {
            if (_comments.ContainsKey(key))
                return _comments[key];
            else
                return null;
        }

        public void SetComment(string key, string value) {
            _comments[key] = value;
            HasChanged = true;
        }

	}
}
